using UnityEngine;
using System.Collections;

public class Hero : Character 
{
    protected Transform mRightWeapon;
    protected Transform mLeftWeapon;
    protected Transform mRightShoulder;
    protected Transform mLeftShoulder;

    //Anchors for Gear Objects
    protected Transform mRightWeaponAttach;
    protected Transform mLeftWeaponAttach;
    protected Transform mRightShoulderAttach;
    protected Transform mLeftShoulderAttach;

    protected CharacterController mController;
    protected HeroMovement mMovement;
    //protected Vector3 mMovement;
    public float mCurrentSpeed = 5.0f;

    // Use this for initialization
    protected override void Start()
    {
        base.Start();
        mController = gameObject.GetComponent<CharacterController>();
        mMovement = new HeroMovement();
        mAnimation["JumpLand"].wrapMode = WrapMode.ClampForever;
        mAnimation["JumpLandRun"].wrapMode = WrapMode.ClampForever;
        mAnimation["JumpLandRun"].speed = 1.2f;
    }

    protected override void FixedUpdate()
    {
        base.FixedUpdate();
        mTransform.position = new Vector3(mTransform.position.x, mTransform.position.y, 0.0f);
    }

    // Update is called once per frame
    protected override void Update()
    {

        if (Input.GetButtonDown("Jump") && mMovement.mCanControl)
        {
            mMovement.mLastInputTime = Time.time;
        }

        UpdateMovement();

        // Apply gravity
        // - extra power jump modifies gravity
        ApplyGravity();

        // Apply jumping logic
        ApplyJumping();

        // Moving platform support
        /*if (activePlatform != null)
        {
            var newGlobalPlatformPoint = activePlatform.TransformPoint(activeLocalPlatformPoint);
            var moveDistance = (newGlobalPlatformPoint - activeGlobalPlatformPoint);
            transform.position = transform.position + moveDistance;
            lastPlatformVelocity = (newGlobalPlatformPoint - activeGlobalPlatformPoint) / Time.deltaTime;
        }
        else
        {
            lastPlatformVelocity = Vector3.zero;
        }

        activePlatform = null;*/

        // Save lastPosition for velocity calculation.
        Vector3 lastPosition = mTransform.position;

        // Calculate actual motion
        Vector3 currentMovementOffset = mMovement.mDirection * mMovement.mSpeed + new Vector3(0.0f, mMovement.mVerticalSpeed, 0.0f) + mMovement.mInAirVelocity;

        // We always want the movement to be framerate independent.  Multiplying by Time.deltaTime does this.
        currentMovementOffset *= Time.deltaTime;

        // Move our character!
        mMovement.mCollisionFlags = mController.Move(currentMovementOffset);

        // Calculate the velocity based on the current and previous position.  
        // This means our velocity will only be the amount the character actually moved as a result of collisions.
        mMovement.mVelocity = (mTransform.position - lastPosition) / Time.deltaTime;

        // Moving platforms support
        /*if (activePlatform != null)
        {
            activeGlobalPlatformPoint = transform.position;
            activeLocalPlatformPoint = activePlatform.InverseTransformPoint(transform.position);
        }*/

        // Set rotation to the move direction	
        if (mMovement.mDirection.sqrMagnitude > 0.01)
        {
            mTransform.rotation = Quaternion.Slerp(mTransform.rotation, Quaternion.LookRotation(mMovement.mDirection), Time.deltaTime * HeroMovement.ROTATION_SMOOTHING);
        }

        // We are in jump mode but just became grounded
        if (mController.isGrounded)
        {
            mMovement.mInAirVelocity = Vector3.zero;
            if(mMovement.mIsJumping)
            {
                mMovement.mIsJumping = false;
                //SendMessage("DidLand", SendMessageOptions.DontRequireReceiver);

                SwitchJumpState(E_JUMP_STATES.Jump_Land);

                var jumpMoveDirection = mMovement.mDirection * mMovement.mSpeed + mMovement.mInAirVelocity;
                if (jumpMoveDirection.sqrMagnitude > 0.01)
                {
                    mMovement.mDirection = jumpMoveDirection.normalized;
                }
            }
        }

        // Update special effects like rocket pack particle effects
        //UpdateEffects();

        switch (mCurrentState)
        {
            case E_CHARACTER_STATES.Character_State_Idle:
            {
                IdleState();
                break;
            }
            case E_CHARACTER_STATES.Character_State_Run:
            {
                RunState();
                break;
            }
            case E_CHARACTER_STATES.Character_State_Walk:
            {
                WalkState();
                break;
            }
            case E_CHARACTER_STATES.Character_State_WalkBackward:
            {
                WalkBackwardState();
                break;
            }
            case E_CHARACTER_STATES.Character_State_Jump:
            {
                JumpState();
                break;
            }
        }

        if (mNextState != mCurrentState)
        {
            SwitchState(mNextState);
        }
    }

    void IdleState()
    {
        if (Input.GetAxisRaw("Horizontal") > 0.0f && Input.GetAxisRaw("Horizontal") < 0.5f)
        {
            mNextState = E_CHARACTER_STATES.Character_State_Walk;
        }
        else if (Input.GetAxisRaw("Horizontal") >= 0.5f)
        {
            mNextState = E_CHARACTER_STATES.Character_State_Run;
        }
        else if (Input.GetAxisRaw("Horizontal") < 0.0f)
        {
            //mNextState = E_CHARACTER_STATES.Character_State_WalkBackward;
            mNextState = E_CHARACTER_STATES.Character_State_Run;
        }

        //MovePlayer();
    }

    void WalkState()
    {
        mCurrentSpeed = HeroMovement.WALK_SPEED;

        if (Input.GetAxisRaw("Horizontal") >= 0.5f)
        {
            mNextState = E_CHARACTER_STATES.Character_State_Run;
        }
        else if (Input.GetAxisRaw("Horizontal") < 0.0f)
        {
            //mNextState = E_CHARACTER_STATES.Character_State_WalkBackward;
            mNextState = E_CHARACTER_STATES.Character_State_Run;
        }
        else if (Input.GetAxisRaw("Horizontal") == 0.0f)
        {
            mNextState = E_CHARACTER_STATES.Character_State_Idle;
        }

        //MovePlayer();
    }

    void RunState()
    {
        mCurrentSpeed = HeroMovement.RUN_SPEED;

        if (Input.GetAxisRaw("Horizontal") > 0.0f && Input.GetAxisRaw("Horizontal") < 0.5f)
        {
            mNextState = E_CHARACTER_STATES.Character_State_Walk;
        }
        else if (Input.GetAxisRaw("Horizontal") < 0.0f)
        {
            //mNextState = E_CHARACTER_STATES.Character_State_WalkBackward;
            mNextState = E_CHARACTER_STATES.Character_State_Run;
        }
        else if (Input.GetAxisRaw("Horizontal") == 0.0f)
        {
            mNextState = E_CHARACTER_STATES.Character_State_Idle;
        }

        //MovePlayer();
    }

    void WalkBackwardState()
    {
        mCurrentSpeed = HeroMovement.WALKBACK_SPEED;

        if (Input.GetAxisRaw("Horizontal") > 0.0f && Input.GetAxisRaw("Horizontal") < 0.5f)
        {
            mNextState = E_CHARACTER_STATES.Character_State_Walk;
        }
        else if (Input.GetAxisRaw("Horizontal") > 0.0f && Input.GetAxisRaw("Horizontal") >= 0.5f)
        {
            mNextState = E_CHARACTER_STATES.Character_State_Run;
        }
        else if (Input.GetAxisRaw("Horizontal") == 0.0f)
        {
            mNextState = E_CHARACTER_STATES.Character_State_Idle;
        }

        //MovePlayer();
    }

    void JumpState()
    {

    }

    void MovePlayer()
    {
        mController.Move(GetMovement() * Time.deltaTime * mCurrentSpeed);
    }

    Vector3 GetMovement()
    {
        float x = Mathf.Ceil(Input.GetAxisRaw("Horizontal"));
        return (new Vector3(x, Physics.gravity.y * 2, 0));
    }

    void UpdateMovement()
    {
        float h = Input.GetAxisRaw("Horizontal");

        if (!mMovement.mCanControl)
        {
            h = 0.0f;
        }

        mMovement.mIsMoving = Mathf.Abs(h) > 0.1;

        if (mMovement.mIsMoving)
        {
            mMovement.mDirection = new Vector3(h, 0, 0);
        }

        // Grounded controls
        if (mController.isGrounded)
        {
            // Smooth the speed based on the current target direction
            float curSmooth = HeroMovement.SPEED_SMOOTHING * Time.deltaTime;

            // Choose target speed
            float targetSpeed = Mathf.Min(Mathf.Abs(h), 1.0f);

            // Pick speed modifier
            /*if (Input.GetButton("Fire2") && canControl)
            {
                targetSpeed *= HeroMovement.RUN_SPEED;
            }
            else
            {
                targetSpeed *= movement.walkSpeed;
            }*/
            targetSpeed *= HeroMovement.RUN_SPEED;

            //mMovement.mSpeed = Mathf.Lerp(mMovement.mSpeed, targetSpeed, curSmooth);
            mMovement.mSpeed = targetSpeed * Mathf.Abs(h);

            mMovement.mHangTime = 0.0f;
        }
        else
        {
            // In air controls
            mMovement.mHangTime += Time.deltaTime;
            if (mMovement.mIsMoving)
            {
                //mMovement.mInAirVelocity += new Vector3(Mathf.Sign(h), 0, 0) * Time.deltaTime * HeroMovement.IN_AIR_CONTROL_ACCELERATION;
                mMovement.mInAirVelocity = Vector3.zero;
                mMovement.mSpeed = HeroMovement.RUN_SPEED * Mathf.Abs(h);
            }
            else
            {
                mMovement.mSpeed -= HeroMovement.IN_AIR_CONTROL_DECELERATION * Time.deltaTime;
                if(mMovement.mSpeed < 0.0f)
                {
                    mMovement.mSpeed = 0.0f;
                }
            }
        }
    }

    void ApplyJumping()
    {
        // Prevent jumping too fast after each other 
        if (mMovement.mLastInputTime + HeroMovement.JUMP_INPUT_REPEAT_TIME > Time.time)
        {
            return;
        }

        if (mController.isGrounded)
        {
            // Jump
            // - Only when pressing the button down
            // - With a timeout so you can press the button slightly before landing		
            if (mMovement.mCanJump && Time.time < mMovement.mLastInputTime + HeroMovement.JUMP_TIMEOUT)
            {
                DidJump();
                mNextState = E_CHARACTER_STATES.Character_State_Jump;
                mMovement.mVerticalSpeed = CalculateJumpVerticalSpeed(HeroMovement.BASE_JUMP_MIN_FORCE);
                //mMovement.mInAirVelocity = lastPlatformVelocity;
                //SendMessage("DidJump", SendMessageOptions.DontRequireReceiver);
            }
        }
    }

    void ApplyGravity()
    {
        // Apply gravity 
        bool jumpButton = Input.GetButton("Jump");

        if (!mMovement.mCanControl)
        {
            jumpButton = false;
        }

        // When we reach the apex of the jump we send out a message
        if (mMovement.mIsJumping && !mMovement.mReachedApex && mMovement.mVerticalSpeed <= 0.0)
        {
            mMovement.mReachedApex = true;
            SwitchJumpState(E_JUMP_STATES.Jump_Loop);
            //SendMessage("DidJumpReachApex", SendMessageOptions.DontRequireReceiver);
        }

        // * When jumping up we don't apply gravity for some time when the user is holding the jump button
        //   This gives more control over jump height by pressing the button longer
        bool extraPowerJump = /*mMovement.mIsJumping && */mMovement.mVerticalSpeed > 0.0f && jumpButton && mTransform.position.y < (mMovement.mLastStartingHeight + HeroMovement.BASE_JUMP_ADD_FORCE) && !IsTouchingCeiling();

        //Debug.Log("IsJumping: " + mMovement.mIsJumping + " VerticalSpeed: " + mMovement.mVerticalSpeed + " jumpButton: " + jumpButton);
        Debug.Log("Extra Jump Power: " + extraPowerJump);

        if (extraPowerJump)
        {
            mMovement.mVerticalSpeed += HeroMovement.BASE_JUMP_ADD_FORCE;
        }
        
        if (mController.isGrounded)
        {
            mMovement.mVerticalSpeed = HeroMovement.GRAVITY * Time.deltaTime;
        }
        else
        {
            mMovement.mVerticalSpeed += HeroMovement.GRAVITY * Time.deltaTime;
        }

        // Make sure we don't fall any faster than maxFallSpeed.  This gives our character a terminal velocity.
        mMovement.mVerticalSpeed = Mathf.Max(mMovement.mVerticalSpeed, -HeroMovement.MAX_FALL_SPEED);
    }

    float CalculateJumpVerticalSpeed (float aTargetJumpHeight) 
    { 
	    // From the jump height and gravity we deduce the upwards speed 
	    // for the character to reach at the apex. 
        //return Mathf.Sqrt(2 * aTargetJumpHeight * Physics.gravity.y); 
        return HeroMovement.BASE_JUMP_MIN_FORCE;
    }

    void DidJump () 
    { 
	    mMovement.mIsJumping = true; 
	    mMovement.mReachedApex = false;
        mMovement.mLastJumpTime = Time.time;
        mMovement.mLastStartingHeight = transform.position.y; 
	    mMovement.mLastInputTime = -10.0f; 
    }

    void OnControllerColliderHit(ControllerColliderHit hit)
    {
        if (hit.moveDirection.y > 0.01)
        {
            return;
        }

        // Make sure we are really standing on a straight platform
        // Not on the underside of one and not falling down from it either!
        /*if (hit.moveDirection.y < -0.9 && hit.normal.y > 0.9)
        {
            activePlatform = hit.collider.transform;
        }*/
    }

    public void SwitchState(E_CHARACTER_STATES aState)
    {
        switch (aState)
        {
            case E_CHARACTER_STATES.Character_State_Idle:
            {
                if (mCurrentState == E_CHARACTER_STATES.Character_State_Jump)
                {
                    mAnimation.CrossFade("Stand01", 1.0f);
                }
                else
                {
                    mAnimation.CrossFade("Stand01", 0.2f);
                }
                break;
            }

            case E_CHARACTER_STATES.Character_State_Walk:
            {
                mAnimation.CrossFade("Walk", 0.05f);
                break;
            }

            case E_CHARACTER_STATES.Character_State_Run:
            {
                mAnimation.CrossFade("Run", 0.05f);
                break;
            }

            case E_CHARACTER_STATES.Character_State_WalkBackward:
            {
                mAnimation.CrossFade("WalkBackward", 0.05f);
                break;
            }

            case E_CHARACTER_STATES.Character_State_Jump:
            {
                //mAnimation.CrossFade("JumpStart");
                SwitchJumpState(E_JUMP_STATES.Jump_Start);
                break;
            }

            default:
            {
                Debug.LogError("Unhandled Character State Inside Hero::SwitchState -> " + aState);
                break;
            }
        }

        mCurrentState = aState;
    }

    public void SwitchJumpState(E_JUMP_STATES aState)
    {
        switch (aState)
        {
            case E_JUMP_STATES.Jump_Start:
            {
                Debug.Log("SwitchJumpState::Jump_Start");
                mAnimation.CrossFade("JumpStart", 0.01f);
                break;
            }

            case E_JUMP_STATES.Jump_Loop:
            {
                Debug.Log("SwitchJumpState::Jump_Loop");
                mAnimation.CrossFade("JumpLoop", 2.0f);
                break;
            }

            case E_JUMP_STATES.Jump_Land:
            {
                Debug.Log("SwitchJumpState::Jump_Land");
                if (mMovement.mIsMoving)
                {
                    mAnimation.CrossFade("JumpLandRun", 0.05f);
                }
                else
                {
                    mAnimation.CrossFade("JumpLand", 0.05f);
                }
                break;
            }

            case E_JUMP_STATES.Jump_Done:
            {
                Debug.Log("SwitchJumpState::Jump_Done");
                if (mMovement.mIsMoving)
                {
                    mNextState = E_CHARACTER_STATES.Character_State_Run;
                }
                else
                {
                    mNextState = E_CHARACTER_STATES.Character_State_Idle;
                }
                break;
            }

            default:
            {
                Debug.LogError("Unhandled Character State Inside Hero::SwitchJumpState -> " + aState);
                break;
            }
        }
    }

    public bool IsTouchingCeiling() { return (mMovement.mCollisionFlags & CollisionFlags.CollidedAbove) != 0; }
}